#!/usr/bin/env bash

include "test.sh"
include "object.sh"

object::define_struct Parallel "
    --cargs=fifopath,ncpu
    --string=fifopath
    --string=ncpu
    --array=pending_tasks
    --array=current_tasks
"

function Parallel::MakeFromNCPU()
{
    local obj="$1"
    local ncpu="${2-4}"
    Parallel "$obj" "$(mktemp --dry-run "/tmp/parallel.XXXXXXXX")" "$ncpu"
}

function Parallel::AddCommand()
{
    local obj="$1"
    local command="$2"
    Parallel::pending_tasks "$obj" push "$command"
}

function Parallel::Run()
{
    local obj="$1"
    local fifopath="$(Parallel::fifopath "$obj")"
    local ncpu="$(Parallel::ncpu "$obj")"

    function Parallel::RunTask()
    {
        local command="$1"
        debuglog "task: $command"
        eval "$command"
        echo "$BASHPID" > "$fifopath"
        debuglog "task: $command done"
        exit
    }

    if ! mkfifo -m 777 "$fifopath"; then
        loge "mkfifo $fifopath失败"
        return 1
    fi

    local i
    for (( i = 0; i < $(Parallel::pending_tasks "$obj" len); )); do
        local command="$(Parallel::pending_tasks "$obj" "$i")"
        debuglog "process $i: $command"
        if (( $(Parallel::current_tasks "$obj" len) < $ncpu )); then
            Parallel::RunTask "$command" &
            Parallel::current_tasks "$obj" push "$!"
            debuglog "running $!"
            (( i++ ))
            continue
        fi

        debuglog "waiting $i: $command"
        while read pid; do
            debuglog "reading $pid"
            Parallel::current_tasks "$obj" erase "$pid"
        done <<< $(cat < "$fifopath")
    done

    while (( "$(Parallel::current_tasks "$obj" len)" > 0 )); do
        while read pid; do
            debuglog "reading $pid"
            Parallel::current_tasks "$obj" erase "$pid"
        done <<< $(cat < "$fifopath")
    done

    rm "$fifopath"
}

function Parallel::test1()
{
    function Parallel::test1::task()
    {
        debuglog "$1: sleep $(( $1 / 4 ))"
        sleep "$(( $1 / 4 ))"
    }

    local stime="$(date +%s%N)"
    Parallel::MakeFromNCPU parallel
    local i
    for (( i = 0; i < 16; i++ )); do
        Parallel::AddCommand parallel "Parallel::test1::task $i"
    done

    Parallel::Run parallel
    test::run_cmd echo $(( ($(date +%s%N) - $stime) / 1000 / 1000 ))
    test::should_in 6000 6300
}
test::register_test Parallel::test1
